﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using PloobsEngine.Light;
using PloobsEngine.Draw;

namespace PloobsEngine.SceneControl
{

    public enum RestoreDepthOption
    {
        NONE, BEFORE_POSTEFFECT, AFTER_POSTEFFECT
    }

    public class DeferredRenderTechnic : IRenderTechnic
    {
        private IDeferredGBuffer deferredGBuffer = new GBuffer();
        private IDeferredLightMap deferredLightMap = new LightMap();
        private IDeferredFinalCombination deferredFinalCombination = new FinalCombination();
        private SpriteBatch spriteBatch;
        protected EngineStuff engine;
        private bool debug = false;
        private bool phusicDebug = false;
        private bool lightDebug = false;
        private bool lightDebugInit = false;
        private Model sphereModel;
        private Model coneModel;
        private IForwardPass forwardPass = new ForwardPass();
        private BasicEffect basicEffect;
        private IList<IPostEffect> PostEffects = new List<IPostEffect>();
        private QuadRender qrender;
        public const String colorRT = "COLORRT";
        public const String normalRt = "NORMALRT";
        public const String lightRt = "LIGHTRT";
        public const String dephRT = "DEPHRT";
        private RenderTarget2D target;
        private Rectangle screenRect;
        private RestoreDepthOption restoreDepth = RestoreDepthOption.AFTER_POSTEFFECT;
        private RestoreDepth rd;
        private bool useFloatingBufferForLightMap = true;
        private NonePostEffect none;
        private bool isDebugImageEnabled = false;

        public bool IsDebugImageEnabled
        {
            get { return isDebugImageEnabled; }
            set { isDebugImageEnabled = value; }
        }

        static List<DeferredDebugImageRender> debugImages = new List<DeferredDebugImageRender>();

        public static List<DeferredDebugImageRender> DebugImages
        {
            get { return DeferredRenderTechnic.debugImages; }            
        }

        private String[] debugRTs = new String[4] { PrincipalConstants.CurrentImage, PrincipalConstants.normalRt, PrincipalConstants.colorRT, PrincipalConstants.lightRt };

        public String[] DebugRTs
        {
            get { return debugRTs; }
            set { debugRTs = value; }
        }                   

        public bool UseFloatingBufferForLightMap
        {
            get { return useFloatingBufferForLightMap; }
            set { useFloatingBufferForLightMap = value; }
        }

        public RestoreDepthOption RestoreDepth
        {
            get { return restoreDepth; }
            set { restoreDepth = value; }
        }

        /// <summary>
        /// Adiciona post effects
        /// </summary>
        /// <param name="postEffect">Lista de efeitos</param>
        /// <param name="replaceExisting">Remove os efeitos atuais ?</param>
        public void AddPostEffect(IPostEffect[] postEffect, bool replaceExisting)
        {
            if (replaceExisting || PostEffects.Where((aa, bb) => { return aa is NonePostEffect; }).Count() != 0)
            {
                PostEffects.Clear();
            }

            foreach (IPostEffect item in postEffect)
            {
                item.init(engine);
                PostEffects.Add(item);
            }
        }

        /// <summary>
        /// Adiciona um efeito
        /// </summary>
        /// <param name="postEffect"></param>        
        public void AddPostEffect(IPostEffect postEffect)
        {
            if (PostEffects.Where((aa, bb) => { return aa is NonePostEffect; }).Count() != 0)
            {
                PostEffects.Clear();
            }

            postEffect.init(engine);
            PostEffects.Add(postEffect);
        }

        /// <summary>
        /// Remove um efeito especifico
        /// </summary>
        /// <param name="postEffect"></param>
        public void RemoveEffect(IPostEffect postEffect)
        {
            PostEffects.Remove(postEffect);
            if (PostEffects.Count == 0)
            {
                NonePostEffect none = new NonePostEffect();
                none.init(engine);
                this.PostEffects.Add(none);
            }
        }

        /// <summary>
        /// Retorna uma lista com todos os efeitos
        /// </summary>
        /// <returns></returns>
        public IList<IPostEffect> PostEffectsList()
        {
            if (PostEffects.Where((aa, bb) => { return aa is NonePostEffect; }).Count() != 0)
            {
                return null;
            }
            return PostEffects;
        }

        /// <summary>
        /// Carrega uma lista de efeitos
        /// </summary>
        public IList<IPostEffect> PostEffectList
        {
            set
            {
                PostEffects.Clear();
                if (value == null)
                {
                    NonePostEffect none = new NonePostEffect();
                    none.init(engine);
                    this.PostEffects.Add(none);
                }
                else
                {
                    foreach (IPostEffect item in value)
                    {
                        item.init(engine);
                        this.PostEffects.Add(item);
                    }
                }
            }
        }

        public IForwardPass ForwardPass
        {
            get { return forwardPass; }
            set { forwardPass = value; }
        }

        public IDeferredLightMap DeferredLightMap
        {
            set
            {
                this.deferredLightMap = value;
            }
            get
            {
                return deferredLightMap;
            }
        }

        public IDeferredGBuffer DeferredGBuffer
        {
            set
            {
                this.deferredGBuffer = value;
            }
            get
            {
                return this.deferredGBuffer;
            }
        }
        public IDeferredFinalCombination DeferredFinalCombination
        {
            get
            {
                return deferredFinalCombination;
            }
            set
            {
                this.deferredFinalCombination = value;
            }
        }
        public bool isDebug
        {
            set
            {
                this.debug = value;
            }
            get
            {
                return this.debug;
            }
        }
        public bool isPhysicDebug
        {
            set
            {
                this.phusicDebug = value;
            }
            get
            {
                return this.phusicDebug;
            }
        }

        public bool isLightDebug
        {
            set
            {
                this.lightDebug = value;
            }
            get
            {
                return this.lightDebug;
            }
        }


        public DeferredRenderTechnic(EngineStuff engine)
        {
            this.engine = engine;
        }

        public virtual void Initialization(IRenderHelper render, IWorld world, EngineStuff engine)
        {
            spriteBatch = new SpriteBatch(EngineStuff.GraphicsDevice);
            DeferredGBuffer.LoadContent(engine, render);
            DeferredLightMap.LoadContent(engine, useFloatingBufferForLightMap);
            DeferredFinalCombination.LoadContent(engine, useFloatingBufferForLightMap);
            //DeferredFinalCombination.SaveToTexture = true;                        
            target = EngineStuff.GetDefaultColorBuffer();
            qrender = new QuadRender(EngineStuff.GraphicsDevice);
            rd = new RestoreDepth(engine, useFloatingBufferForLightMap);

        }

        public virtual void ExecuteTechnic(IRenderHelper render, IWorld world)
        {
            Draw(EngineStuff.GetCurrentGameTime(), world, render);
        }

        
        protected void Draw(GameTime gameTime, IWorld world, IRenderHelper render)
        {            
            EngineStuff.setCurrentEngineProcessingState(EngineProcessingState.DeferredDraw);
            DeferredGBuffer.PreDrawScene(gameTime, world);
            world.Culler.StartFrame(world,world.ActiveCamera);
            DeferredGBuffer.SetGBuffer();
            DeferredGBuffer.ClearGBuffer();
            DeferredGBuffer.DrawScene(gameTime, world);
            DeferredGBuffer.ResolveGBuffer();
            DeferredLightMap.SetLightMap();
            DeferredLightMap.DrawLights(gameTime, world, DeferredGBuffer);
            DeferredLightMap.ResolveLightMap();            
            DeferredFinalCombination.SetFinalCombination();
            DeferredFinalCombination.DrawScene(qrender, gameTime, world, DeferredGBuffer, DeferredLightMap);

            

            ///se nao for pra salvar em textura, nao tera debug nem post processing ... 
            if (DeferredFinalCombination.SaveToTexture)
            {
                render[DeferredRenderTechnic.colorRT] = DeferredGBuffer[GBufferTypes.COLOR];
                render[DeferredRenderTechnic.normalRt] = DeferredGBuffer[GBufferTypes.NORMAL];
                render[DeferredRenderTechnic.lightRt] = DeferredLightMap[DeferredLightMapType.LIGHTMAP];
                render[DeferredRenderTechnic.dephRT] = DeferredGBuffer[GBufferTypes.DEPH];
                render[PrincipalConstants.extra1RT] = DeferredGBuffer[GBufferTypes.Extra1];
                render[PrincipalConstants.CombinedImage] = DeferredFinalCombination[GBufferTypes.FINALIMAGE];
                render[PrincipalConstants.CurrentImage] = DeferredFinalCombination[GBufferTypes.FINALIMAGE];

                if (restoreDepth == RestoreDepthOption.BEFORE_POSTEFFECT)
                {
                    EngineStuff.setCurrentEngineProcessingState(EngineProcessingState.DepthPass);
                    rd.PerformForwardPass(render[PrincipalConstants.CombinedImage], render[PrincipalConstants.dephRT], qrender);
                    if (isPhysicDebug)
                    {
                        world.PhysicWorld.DebugDrawn(EngineStuff.GetCurrentGameTime(), world.ActiveCamera);
                    }
                    world.ParticleManager.Draw(render, EngineStuff.GetCurrentGameTime(), world.ActiveCamera);
                    forwardPass.Draw(gameTime, world, render);
                    engine.PosRenderWithDepth(gameTime);                                        
                    Drawing.DrawAll(world.ActiveCamera);
                    render[PrincipalConstants.CurrentImage] = rd.EndForwardPass();
                    render[PrincipalConstants.CombinedImage] = render[PrincipalConstants.CurrentImage];
                    EngineStuff.setCurrentEngineProcessingState(EngineProcessingState.DeferredDraw);
                }


                int max = PostEffects.Count - 1;
                if (restoreDepth == RestoreDepthOption.AFTER_POSTEFFECT)
                    max += 1;
                for (int i = 0; i < max; i++)
                {
                    EngineStuff.GraphicsDevice.SetRenderTarget(0, target);
                    PostEffects[i].Draw(render, EngineStuff.GetCurrentGameTime(), new Rectangle(0, 0, target.Width, target.Height), world);
                    EngineStuff.GraphicsDevice.SetRenderTarget(0, null);
                    render[PrincipalConstants.CurrentImage] = target.GetTexture();                    
                }

                if (restoreDepth == RestoreDepthOption.AFTER_POSTEFFECT)
                {
                    EngineStuff.setCurrentEngineProcessingState(EngineProcessingState.DepthPass);
                    rd.PerformForwardPass(render[PrincipalConstants.CurrentImage], render[PrincipalConstants.dephRT], qrender);
                    if (isPhysicDebug)
                    {
                        world.PhysicWorld.DebugDrawn(EngineStuff.GetCurrentGameTime(), world.ActiveCamera);
                    }
                    world.ParticleManager.Draw(render, EngineStuff.GetCurrentGameTime(), world.ActiveCamera);
                    forwardPass.Draw(gameTime, world, render);
                    engine.PosRenderWithDepth(gameTime);
                    Drawing.DrawAll(world.ActiveCamera);
                    render[PrincipalConstants.CurrentImage] = rd.EndForwardPass();
                    none.Draw(render, EngineStuff.GetCurrentGameTime(), screenRect, world);
                    EngineStuff.setCurrentEngineProcessingState(EngineProcessingState.DeferredDraw);

                }
                else
                {
                    EngineStuff.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);
                    PostEffects[PostEffects.Count - 1].Draw(render, EngineStuff.GetCurrentGameTime(), screenRect, world);
                }

                engine.PosRender(EngineStuff.GetCurrentGameTime(),world.ActiveCamera.View,world.ActiveCamera.Projection);

                if (isDebugImageEnabled)
                {
                    spriteBatch.Begin(SpriteBlendMode.None);
                    foreach (var item in debugImages)
                    {
                        spriteBatch.Draw(item.ToBeRendered ,item.Destiny , Color.White);                
                    }
                    spriteBatch.End();
                    debugImages.Clear();
                }

                if (debug)
                {
                    int halfWidth = EngineStuff.GraphicsDevice.Viewport.Width / 2;
                    int halfHeight = EngineStuff.GraphicsDevice.Viewport.Height / 2;
                    spriteBatch.Begin(SpriteBlendMode.None);

                    spriteBatch.Draw(render[debugRTs[0]], new Rectangle(0, 0, halfWidth, halfHeight), Color.White);
                    spriteBatch.Draw(render[debugRTs[1]], new Rectangle(0, halfHeight, halfWidth, halfHeight), Color.White);
                    spriteBatch.Draw(render[debugRTs[2]], new Rectangle(halfWidth, 0, halfWidth, halfHeight), Color.White);
                    spriteBatch.Draw(render[debugRTs[3]], new Rectangle(halfWidth, halfHeight, halfWidth, halfHeight), Color.White);

                    spriteBatch.End();
                }

                if (lightDebug)
                {
                    FillMode fm = EngineStuff.GraphicsDevice.RenderState.FillMode;
                    CullMode cm = EngineStuff.GraphicsDevice.RenderState.CullMode;

                    if (!lightDebugInit)
                    {
                        sphereModel = EngineStuff.InternalContentManager.GetAsset<Model>("Dsphere");
                        coneModel = EngineStuff.InternalContentManager.GetAsset<Model>("cone");
                        basicEffect = new BasicEffect(engine.Game.GraphicsDevice, null);
                    }


                    IList<ILight> lights = world.Lights;
                    foreach (var item in lights)
                    {
                        if (!(item is DeferredLight))
                            throw new Exception("Wrong Light Type");

                        DeferredLight dflt = item as DeferredLight;
                        switch (dflt.LightType)
                        {
                            case LightType.Deferred_Point:
                                {
                                    PointLight pl = dflt as PointLight;
                                    Matrix sphereWorldMatrix = Matrix.CreateScale(pl.LightRadius) * Matrix.CreateTranslation(pl.LightPosition);

                                    foreach (ModelMesh mesh in sphereModel.Meshes)
                                    {
                                        Matrix[] boneTransforms = new Matrix[sphereModel.Bones.Count];
                                        sphereModel.CopyBoneTransformsTo(boneTransforms);

                                        foreach (ModelMeshPart meshPart in mesh.MeshParts)
                                        {
                                            EngineStuff.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
                                            EngineStuff.GraphicsDevice.RenderState.CullMode = CullMode.None;

                                            this.basicEffect.View = world.ActiveCamera.View;
                                            this.basicEffect.Projection = world.ActiveCamera.Projection;
                                            this.basicEffect.VertexColorEnabled = true;
                                            Matrix wld = sphereWorldMatrix;
                                            wld = Matrix.Multiply(wld, boneTransforms[mesh.ParentBone.Index]);
                                            this.basicEffect.World = wld;
                                            EngineStuff.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                                            EngineStuff.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                                            EngineStuff.GraphicsDevice.Indices = mesh.IndexBuffer;

                                            this.basicEffect.Begin();
                                            this.basicEffect.CurrentTechnique.Passes[0].Begin();
                                            EngineStuff.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, meshPart.BaseVertex, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
                                            this.basicEffect.CurrentTechnique.Passes[0].End();
                                            this.basicEffect.End();

                                        }
                                    }
                                    break;
                                }
                            case LightType.Deferred_Spot:
                                {
                                    SpotLight sl = dflt as SpotLight;
                                    const float HEIGHT = 100;
                                    Vector3 pos = sl.Position;
                                    //Matrix sphereWorldMatrix = Matrix.CreateScale(HEIGHT * (float)Math.Tan(Math.Acos(sl.LightAngleCosine)), HEIGHT, HEIGHT * (float)Math.Tan(Math.Acos(sl.LightAngleCosine))) * Matrix.CreateWorld(pos, pos + sl.Direction, Vector3.Up);                                    
                                    Vector3 first = new Vector3(1, 0, 0);
                                    Vector3 second = sl.Direction;
                                    second.Normalize();
                                    float dotproductx = Vector3.Dot(first, second);// Take arc cosine of our dot product to give us the angle
                                    float anglex = (float)Math.Acos(dotproductx);

                                    first = new Vector3(0, 1, 0);
                                    float dotproducty = Vector3.Dot(first, second);// Take arc cosine of our dot product to give us the angle
                                    float angley = (float)Math.Acos(dotproducty);

                                    first = new Vector3(0, 0, 1);
                                    float dotproductz = Vector3.Dot(first, second);// Take arc cosine of our dot product to give us the angle
                                    float anglez = (float)Math.Acos(dotproductz);

                                    Matrix h = Matrix.CreateRotationY(MathHelper.ToRadians(-90));

                                    //Matrix sphereWorldMatrix =  Matrix.CreateWorld(pos, pos + sl.Direction, Vector3.Up);
                                    //Matrix sphereWorldMatrix = Matrix.CreateRotationX(anglex) * h * Matrix.CreateRotationY(-angley) * Matrix.CreateRotationZ(anglez) * Matrix.CreateTranslation(pos); //;
                                    //Matrix sphereWorldMatrix = Matrix.CreateTranslation(pos) ;

                                    Matrix sphereWorldMatrix = Matrix.CreateScale(sl.LightRadius) * Matrix.CreateRotationX(sl.Direction.X) * Matrix.CreateRotationY(sl.Direction.Y) * Matrix.CreateRotationZ(sl.Direction.Z) * Matrix.CreateTranslation(sl.Position);
                                    EngineStuff.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
                                    EngineStuff.GraphicsDevice.RenderState.CullMode = CullMode.None;
                                    DrawModel(coneModel, world.ActiveCamera.View, world.ActiveCamera.Projection, sphereWorldMatrix);

                                    break;
                                }
                        }

                        EngineStuff.GraphicsDevice.RenderState.CullMode = cm;
                        EngineStuff.GraphicsDevice.RenderState.FillMode = fm;
                    }
                }
                EngineStuff.setCurrentEngineProcessingState(EngineProcessingState.NormalDraw);
            }
            else
            {
                engine.PosRender(EngineStuff.GetCurrentGameTime(),world.ActiveCamera.View,world.ActiveCamera.Projection);
            }


        }

        #region IRenderTechnic Members

        public virtual string TechnicName
        {
            get { return "DeferredTechnic"; }
        }

        #endregion

        #region IRenderTechnic Members


        public void AfterLoadContent()
        {
            none = new NonePostEffect();
            none.init(engine);

            if (PostEffects.Count == 0)
            {
                NonePostEffect w = new NonePostEffect();
                w.init(engine);
                PostEffects.Add(w);
            }

            int Width = EngineStuff.BackBufferWidth;
            int Height = EngineStuff.BackBufferHeight;

            int halfWidth = Width / 2;
            int halfHeight = Height / 2;

            if (isDebug)
            {
                screenRect = new Rectangle(halfWidth, halfHeight, halfWidth, halfHeight);
            }
            else
            {
                screenRect = new Rectangle(0, 0, Width, Height);
            }

        }

        #endregion

        private void DrawModel(Model myModel, Matrix view, Matrix projection, Matrix world)
        {
            // Copy any parent transforms.
            Matrix[] transforms = new Matrix[myModel.Bones.Count];
            myModel.CopyAbsoluteBoneTransformsTo(transforms);

            // Draw the model. A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in myModel.Meshes)
            {
                // This is where the mesh orientation is set, as well 
                // as our camera and projection.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * world;
                    effect.View = view;
                    effect.Projection = projection;
                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();
            }

        }

    }



}

